#pragma once

#include <utils/Concepts.h>
#include <utils/Describe.h>
#include <utils/Format.h>
#include <utils/Map.h>
#include <utils/Print.h>
#include <utils/config.h>
#include <utils/para_utils/Utils.h>


#include <boost/describe.hpp>
#include <boost/mp11.hpp>

#include <any>
#include <cstdint>
#include <stdexcept>
#include <string_view>

namespace solar
{
    template <ClassHasName NamedClass>
    void setPara(const std::string& property_type, const std::any& value, NamedClass* object)
    {
        ASSUME(!property_type.empty());
        ASSUME(object);
        auto* private_member_index = internal::describe::getPrivateMemberIndex<NamedClass>();
        auto private_member_index_const =
            internal::describe::getPrivateMemberIndexConst<NamedClass>();
        using D1 = boost::describe::describe_members<std::remove_reference_t<NamedClass>,
                                                     boost::describe::mod_any_access>;
        if (private_member_index->empty()) [[unlikely]]
        {
            // solar::println(solar::format("{} init setPara", NamedClass::name()));
            private_member_index->reserve(boost::mp11::mp_size<D1>::value);
            for (auto& entry : private_member_index_const.data)
            {
                private_member_index->emplace(entry);
            }
        }
        std::uint64_t index = -1;
        private_member_index->visit(property_type, [&](auto& entry) { index = entry.second; });
        try
        {
            internal::setPara<boost::mp11::mp_size<D1>::value>(index, value, object);
        }
        catch (const std::invalid_argument& e)
        {
            using namespace std::string_view_literals;
            if (e.what() == "Wrong index"sv)
            {
                ::solar::println(::solar::format("ERROR: {}::setPara(): No such property: {}",
                                                 NamedClass::name(), property_type));
                throw ::solar::format("ERROR: {}::setPara(): No such property: {}",
                                      NamedClass::name(), property_type);
            }
            ::solar::println(
                ::solar::format("ERROR: {}::setPara(): {}", NamedClass::name(), e.what()));
            throw ::solar::format("ERROR: {}::setPara(): {}", NamedClass::name(), e.what());
        }
    }

    template <ClassHasName NamedClass>
    [[nodiscard]] auto getParaAsAny(const std::string& property_type, NamedClass* object)
        -> std::any
    {
        ASSUME(!property_type.empty());
        ASSUME(object);
        auto* private_member_index = internal::describe::getPrivateMemberIndex<NamedClass>();
        auto private_member_index_const =
            internal::describe::getPrivateMemberIndexConst<NamedClass>();
        using D1 = boost::describe::describe_members<std::remove_reference_t<NamedClass>,
                                                     boost::describe::mod_any_access>;
        if (private_member_index->empty()) [[unlikely]]
        {
            // solar::println(solar::format("{} init getParaAsAny", NamedClass::name()));
            private_member_index->reserve(boost::mp11::mp_size<D1>::value);
            for (auto& entry : private_member_index_const.data)
            {
                private_member_index->emplace(entry);
            }
        }
        std::uint64_t index = -1;
        private_member_index->visit(property_type, [&](auto& entry) { index = entry.second; });
        try
        {
            return internal::getParaAsAny<boost::mp11::mp_size<D1>::value>(index, object);
        }
        catch (const std::invalid_argument& e)
        {
            using namespace std::string_view_literals;
            if (e.what() == "Wrong index"sv)
            {
                ::solar::println(::solar::format("ERROR: {}::getPara(): No such property: {}",
                                                 NamedClass::name(), property_type));
                throw ::solar::format("ERROR: {}::getPara(): No such property: {}",
                                      NamedClass::name(), property_type);
            }
            ::solar::println(
                ::solar::format("ERROR: {}::getPara(): {}", NamedClass::name(), e.what()));
            throw ::solar::format("ERROR: {}::getPara(): {}", NamedClass::name(), e.what());
        }
    }

    template <ClassHasName NamedClass>
    auto getParaPtr(const std::string& property_type, NamedClass* object) -> std::any
    {
        ASSUME(!property_type.empty());
        ASSUME(object);
        auto* private_member_index = internal::describe::getPrivateMemberIndex<NamedClass>();
        auto private_member_index_const =
            internal::describe::getPrivateMemberIndexConst<NamedClass>();
        using D1 = boost::describe::describe_members<std::remove_reference_t<NamedClass>,
                                                     boost::describe::mod_any_access>;
        if (private_member_index->empty()) [[unlikely]]
        {
            // solar::println(solar::format("{} init getParaPtr", NamedClass::name()));
            private_member_index->reserve(boost::mp11::mp_size<D1>::value);
            for (auto& entry : private_member_index_const.data)
            {
                private_member_index->emplace(entry);
            }
        }
        std::uint64_t index = -1;
        private_member_index->visit(property_type, [&](auto& entry) { index = entry.second; });
        try
        {
            return internal::getParaPtr<boost::mp11::mp_size<D1>::value>(index, object);
        }
        catch (const std::invalid_argument& e)
        {
            using namespace std::string_view_literals;
            if (e.what() == "Wrong index"sv)
            {
                ::solar::println(
                    ::solar::format("ERROR: solar::getParaPtr(): [{}] No such property: {}",
                                    NamedClass::name(), property_type));
                throw ::solar::format("ERROR: solar::getParaPtr(): [{}] No such property: {}",
                                      NamedClass::name(), property_type);
            }
            ::solar::println(::solar::format("ERROR: solar::getParaPtr(): [{}] {}",
                                             NamedClass::name(), e.what()));
            throw ::solar::format("ERROR: solar::getParaPtr(): [{}] {}", NamedClass::name(),
                                  e.what());
        }
    }

    template <ClassHasName NamedClass, const char* property_type> consteval auto getParaPtr()
    {
        constexpr auto private_member_index_const =
            internal::describe::getPrivateMemberIndexConst<NamedClass>();
        constexpr auto index = private_member_index_const.at2(property_type);
        static_assert(std::get<1>(index), "No such property");
        using D1 = boost::describe::describe_members<std::remove_reference_t<NamedClass>,
                                                     boost::describe::mod_any_access>;
        using V1 = boost::mp11::mp_at_c<D1, std::get<0>(index)>;
        return V1::pointer;
    }

    template <ClassHasName NamedClass>
    auto getParaAsString(const std::string& property_type, NamedClass* object) -> std::string
    {
        ASSUME(!property_type.empty());
        ASSUME(object);
        auto* private_member_index = internal::describe::getPrivateMemberIndex<NamedClass>();
        auto private_member_index_const =
            internal::describe::getPrivateMemberIndexConst<NamedClass>();
        using D1 = boost::describe::describe_members<std::remove_reference_t<NamedClass>,
                                                     boost::describe::mod_any_access>;
        if (private_member_index->empty()) [[unlikely]]
        {
            // solar::println(solar::format("{} init getParaAsString", NamedClass::name()));
            private_member_index->reserve(boost::mp11::mp_size<D1>::value);
            for (auto& entry : private_member_index_const.data)
            {
                private_member_index->emplace(entry);
            }
        }
        std::uint64_t index = -1;
        private_member_index->visit(property_type, [&](auto& entry) { index = entry.second; });
        try
        {
            auto result = internal::getParaAsString<boost::mp11::mp_size<D1>::value>(index, object);
            if (result.empty())
            {
                ::solar::println(
                    ::solar::format(R"(ERROR: solar::getParaAsString(): Can't convert "
                                        "property "{}" in "{}" to string)",
                                    NamedClass::name(), property_type));
            }
            return result;
        }
        catch (const std::invalid_argument& e)
        {
            using namespace std::string_view_literals;
            if (e.what() == "Wrong index"sv)
            {
                ::solar::println(
                    ::solar::format("ERROR: solar::getParaAsString(): [{}] No such property: {}",
                                    NamedClass::name(), property_type));
                throw ::solar::format("ERROR: getParaPtr(): [{}] No such property: {}",
                                      NamedClass::name(), property_type);
            }
            ::solar::println(::solar::format(R"(ERROR: solar::getParaAsString(): Can't convert "
                                        "property "{}" in "{}" to string)",
                                             NamedClass::name(), property_type));
        }
    }

    template <ClassHasName NamedClass>
    void setParaFromString(const std::string& property_type, const std::string& value,
                           NamedClass* object)
    {
        ASSUME(!property_type.empty());
        ASSUME(!value.empty());
        ASSUME(object);
        auto* private_member_index = internal::describe::getPrivateMemberIndex<NamedClass>();
        auto private_member_index_const =
            internal::describe::getPrivateMemberIndexConst<NamedClass>();
        using D1 = boost::describe::describe_members<std::remove_reference_t<NamedClass>,
                                                     boost::describe::mod_any_access>;
        if (private_member_index->empty()) [[unlikely]]
        {
            // solar::println(solar::format("{} init setParaFromString", NamedClass::name()));
            private_member_index->reserve(boost::mp11::mp_size<D1>::value);
            for (auto& entry : private_member_index_const.data)
            {
                private_member_index->emplace(entry);
            }
        }
        std::uint64_t index = -1;
        private_member_index->visit(property_type, [&](auto& entry) { index = entry.second; });
        try
        {
            internal::setParaFromString<boost::mp11::mp_size<D1>::value>(index, value, object);
        }
        catch (const std::invalid_argument& e)
        {
            using namespace std::string_view_literals;
            if (e.what() == "Wrong index"sv)
            {
                ::solar::println(
                    ::solar::format("ERROR: solar::setParaFromString(): [{}] No such property: {}",
                                    NamedClass::name(), property_type));
                throw ::solar::format(
                    "ERROR: solar::setParaFromString(): [{}] No such property: {}",
                    NamedClass::name(), property_type);
            }
            throw std::logic_error("");
        }
        catch (const std::logic_error&)
        {
            ::solar::println(::solar::format(
                R"(ERROR: solar::setParaFromString(): Can't convert "
                                        "property "{}" in "{}" from string)",
                NamedClass::name(), property_type));
        }
    }

    template <ClassHasName NamedClass>
    auto paraHasValue(const std::string& property_type, NamedClass* object) -> bool
    {
        ASSUME(!property_type.empty());
        ASSUME(object);
        auto* private_member_index = internal::describe::getPrivateMemberIndex<NamedClass>();
        auto private_member_index_const =
            internal::describe::getPrivateMemberIndexConst<NamedClass>();
        using D1 = boost::describe::describe_members<std::remove_reference_t<NamedClass>,
                                                     boost::describe::mod_any_access>;
        if (private_member_index->empty()) [[unlikely]]
        {
            // solar::println(solar::format("{} init paraHasValue", NamedClass::name()));
            private_member_index->reserve(boost::mp11::mp_size<D1>::value);
            for (auto& entry : private_member_index_const.data)
            {
                private_member_index->emplace(entry);
            }
        }
        std::uint64_t index = -1;
        private_member_index->visit(property_type, [&](auto& entry) { index = entry.second; });
        try
        {
            return internal::paraHasValue<boost::mp11::mp_size<D1>::value>(index, object);
        }
        catch (const std::invalid_argument&)
        {
            ::solar::println(
                ::solar::format("ERROR: solar::paraHasValue(): [{}] No such property: {}",
                                NamedClass::name(), property_type));
            throw ::solar::format("ERROR: solar::paraHasValue(): [{}] No such property: {}",
                                  NamedClass::name(), property_type);
        }
    }
} // namespace solar
